package lab_2;


import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import lab_2.windows.AuthorizationDialog;
import lab_2.windows.IMWindow;
import org.jdom2.*;
import org.jdom2.input.SAXBuilder;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import ProtocolXML.newmessage.Message;
import ProtocolXML.newmessage.Message.TypeOfMsg;
import ProtocolXML.newmessage.NotCompatibilityTypeException;
import lab_2.windows.Companion;


/**
 *
 * @author pacific
 */
public class Client extends Thread {


    public static enum ClientState {
        NOT_CONNECTED,
        CONNECTION,
        CONNECTED,
        EXIT
    }

    
    
    public Client(IMWindow window) {
        setWindow(window) ;
    }


    //Try connect to other servers
    private boolean theConnection() {
        for(ServerID server : getServersList()) {
            MyLogger.putMessange("Try connect to server "+server) ;
            getWindow().getStatusBar().setText("Process conenct to "+server) ;
            Socket socketWithServer = server.connect() ;
            MyLogger.putMessange("Connect has been.") ;
            getWindow().getStatusBar().setText("") ;
            if (socketWithServer != null) {
                setSocket(socketWithServer);
                return true;
            }
        }
        return false ;
    }


    /**
     * Main method of class
     */
    @Override
    public void run() {
        MyLogger.putMessange("Run client.");
        loadServers() ;
        boolean toExit = false ;
        while( !toExit ) {
            switch(getStateClient()) {
                //Connect with server is not
                case NOT_CONNECTED : {
                    getWindow().blockWindow() ;
                    MyLogger.putMessange("Are not connect, show autorization frame") ;
                    new AuthorizationDialog(getWindow(), getUsername(), getPassword());
                    break ;
                }
                //Connection in process
                case CONNECTION : {
                    MyLogger.putMessange("Try connect");
                    if ( theConnection() ) {
                        setStateClient(ClientState.CONNECTED);
                        // get input stream
                        try {
                            InputStream inputStream = getSocket().getInputStream();
                            setIStream(inputStream);
                            OutputStream outputStream = getSocket().getOutputStream();
                            setOStream(outputStream);
                        } catch (IOException ex) {
                            MyLogger.putMessange("Not can get Input/Output Stream from Socket. " + ex) ;
                            setStateClient(ClientState.NOT_CONNECTED);
                        }

                        Message autorizMsg = new Message() ;
                        autorizMsg.msgIsAutorization( getUsername(), getPassword() );
                        MyLogger.putMessange("Write authorization message to server.") ;
                        write( autorizMsg ) ;

                    }
                    else {
                        setStateClient( ClientState.NOT_CONNECTED );
                    }
                    break;
                }
                //
                case CONNECTED : {
                    Message message = null ;
                    try {
                        message = Message.read( getIStream() );
                        MyLogger.putMessange( "Receivet msg from server" ) ;
                    } catch (Exception ex) {
                        MyLogger.putMessange( "Not can read message from InputStream " + ex );
                        setStateClient( ClientState.NOT_CONNECTED );
                    }
                    if( message != null ) {
                        handleMessage( message ) ;
                    }
                    else
                        break ;
                    break ;
                }
                case EXIT : {
                    MyLogger.putMessange( "To exit!" ) ;
                    closeClient() ;
                    toExit = true ;
                    break ;
                }
                default : {
                    MyLogger.putMessange( "Getted ERROR state." ) ;
                    System.exit( 0 ) ;
                }
            }
        }
        MyLogger.putMessange( "Stop client." ) ;
    }


    /**
     * Handle message
     * @param message - message for handle
     */
    private void handleMessage( Message message ) {
        //! FIXME Fill method Остались группы
        TypeOfMsg typeOfMsg = message.getTypeOfMsg() ;
        try {
            switch( typeOfMsg ) {
                case SEND_MESSAGE_SRV : {
                    MyLogger.putMessange("Getted SEND_MESSAGE_SRV msg.");
                    int userID = message.getSenderId() ;
                    String msg = message.getMsg() ;
                    for (Companion currentComp : getCompanions()) {
                        if (currentComp.getUserID() == userID) {
                            currentComp.deliverMessage( msg , Companion.Sender.OtherUser) ;
                            //! FIXME Оповестить пользователя о получении сообщения
                            break;
                        }
                    }

                    break ;
                }
                case USER_CONNECT : {
                    int userID = message.getConnectedUserID() ;
                    String nickName = message.getConnectedUserNick() ;
                    Companion comp = new Companion( userID, nickName ) ;
                    getCompanions().add( comp ) ;
                    getWindow().addCompanion( comp ) ;
                    break ;
                }
                case DISCONNECT : {
                    int userID = message.getDisconnectUserId() ;
                    for( Companion currentComp : getCompanions() ) {
                        if( currentComp.getUserID() == userID ) {
                            getWindow().delCompanion( currentComp );
                            getCompanions().remove( currentComp ) ;
                            break ;
                        }
                    }
                    break ;
                }
                case SERVER_INF: {
                    ServerID serverID = message.getServerID() ;
                    getServersList().add( serverID ) ;
                    break ;
                }
                default: {
                    MyLogger.putMessange( "Getted UNEXPECTED message" );
                    //AUTORIZATION, CREATE_GROUP, SEND_MESSAGE_SRV, AUTORIZATION, INV_USER_TO_GROUP, USERS_LIST, SERVER_LIST
                }
            }
        }
        catch(NotCompatibilityTypeException ex) {
            MyLogger.putMessange( "Call method not compatibility with type of message. " + ex );
        }
    }


    /** Write message to server */
    public void write(Message message) {
        try {
            message.write(getOStream());
            getOStream().flush() ;
        } catch (IOException ex) {
            MyLogger.putMessange( "Not can write message in OutputStream. " + ex);
        }
    }

    
    /**Load server and saves them in list for try connect with server*/
    public void closeClient() {
        saveServers() ;
    }
    

    /**Method load configs, servers from file*/
    private final static String XML_SERVER = "Server" ;
    private final static String XML_NAME_SERVER = "Name_of_server" ;
    private final static String XML_HOST = "Host" ;
    private final static String XML_PORT = "Port" ;
    void loadServers() {
        getServersList().add(new ServerID("127.0.0.1", 6666, "Default_1"));
        //getServersList().add(new ServerID("Default_2", "127.0.0.1", 5555)) ;
        //getServersList().add(new ServerID("Default_3", "192.168.5.1", 6666));
        //getServersList().add(new ServerID("Default_4", "192.168.12.1", 6666));
        SAXBuilder builder = new SAXBuilder() ;
        Document document ;
        if( getFileWithServers().canRead() ) {
            try {
                document = builder.build(getFileWithServers()) ;
            }
            catch (JDOMException ex) {
                document = null ;
            }
            catch (IOException ex) {
                document = null ;
            }
            Element root = document.getRootElement() ;
            List<Element> servers = root.getChildren(XML_SERVER) ;
            String name, host ;
            int port ;
            Element currentServer ;
            for( int i=0 ; i<servers.size() ; i++ ) {
                currentServer = servers.get(i) ;
                name = currentServer.getChildText(XML_NAME_SERVER) ;
                host = currentServer.getChildText(XML_HOST) ;
                port = Integer.parseInt(currentServer.getChildText(XML_PORT)) ;
                getServersList().add(new ServerID(host, port, name)) ;
            }

        }
    }


    /**
     * Save list with servers after exit for try connect before new launch
     */
    void saveServers() {
        //XML root's element
        Element root = new Element("List_of_servers") ;
        Document document = new Document(root) ;
        Element exampleServer, name, host, port ;
        for( ServerID currentServer : getServersList() ) {
            //get and put "name server"
            name = new Element(XML_NAME_SERVER) ;
            name.setText(currentServer.getAlias()) ;
            //get and put "host" of server
            host = new Element(XML_HOST) ;
            host.setText(currentServer.getHost()) ;
            //get adn put "port" of server
            port = new Element(XML_PORT) ;
            port.setText(currentServer.getPort()+"") ;
            //Create element for server
            exampleServer = new Element(XML_SERVER) ;
            exampleServer.addContent(name) ;
            exampleServer.addContent(host) ;
            exampleServer.addContent(port) ;
            //add elements in root
            root.addContent(exampleServer) ;
        }
        XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat()) ;

        FileWriter writer = null ;
        try {
            writer = new FileWriter(getFileWithServers());
            outputter.output(document, writer);
            writer.flush() ;
        } catch (IOException ex) {
            MyLogger.putMessange("Not cane write XML file with settings");
        }
        finally {
            try {
                writer.close() ;
            } catch (IOException ex) {
                MyLogger.putMessange("Not cane close Writer for XML file");
            }
        }
    }


    //-----Fields-----

    private ClientState stateClient =  ClientState.NOT_CONNECTED ;

    private String username = "" ;
    private String password = "" ;

    private IMWindow window ;

    private Socket socket ;
    private InputStream iStream ;
    private OutputStream oStream ;

    private Set<ServerID> serversList = new HashSet<ServerID>() ;
    private HashSet<Companion> companions = new HashSet<Companion>() ;

    File fileWithServers = new File( "ServerList.xml" ) ;


    //-----GETTERS and SETTERS-----

    //  for "socket"
    private Socket getSocket() {
        return socket;
    }
    private void setSocket(Socket socket) {
        this.socket = socket;
    }
    //  for "serverList"
    private Set<ServerID> getServersList() {
        return serversList;
    }
    //  for "fileWithServers"
    private File getFileWithServers() {
        return fileWithServers;
    }
    //  for "window"
    private IMWindow getWindow() {
        return window;
    }
    private void setWindow(IMWindow window) {
        this.window = window;
    }
    //  for "stateClient"
    private ClientState getStateClient() {
        return stateClient;
    }
    public void setStateClient(ClientState statusClient) {
        this.stateClient = statusClient;
    }
    //  for "username"
    private synchronized String getUsername() {
        return username;
    }
    public synchronized void setUsername(String username) {
        this.username = username;
    }
    //  for "password"
    private synchronized String getPassword() {
        return password;
    }
    public synchronized void setPassword(String password) {
        this.password = password;
    }
    //For "iStream"
    private InputStream getIStream() {
        return iStream;
    }
    private void setIStream(InputStream iStream) {
        this.iStream = iStream;
    }
    //For "oStream"
    private OutputStream getOStream() {
        return oStream;
    }
    private void setOStream(OutputStream oStream) {
        this.oStream = oStream;
    }

    private HashSet<Companion> getCompanions() {
        return companions;
    }
}
